package com.jiehoo.ooqi;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * All query info needed to construct a QL string.
 */
public class QueryInfo extends SearchInfo implements Serializable {
	private static final long serialVersionUID = 5014901780002173454L;

	public static final String COUNT_COLUMN = "totalrows";

	/**
	 * Select fields.
	 */
	private List<Field> selects = new ArrayList<Field>();

	/**
	 * From targets.
	 */
	private List<From> froms = new ArrayList<From>();

	/**
	 * Group by fields.
	 */
	private List<Field> groupBys = new ArrayList<Field>();

	/**
	 * Having conditions.
	 */
	private List<Condition> havings = new ArrayList<Condition>();

	/**
	 * Native hints.
	 */
	private List<String> hints = new ArrayList<String>();

	/**
	 * Fetch size.
	 */
	private int fetchSize;

	/**
	 * Whether formalized whole query info, such as set default From for all
	 * fields those From is null
	 */
	private boolean formlized;

	public QueryInfo() {
	}

	public List<Field> getSelects() {
		return selects;
	}

	public void setSelects(List<Field> selects) {
		this.selects = selects;
	}

	public void addSelects(List<Field> fields) {
		selects.addAll(fields);
	}

	public void addSelect(Field field) {
		selects.add(field);
	}

	public List<From> getFroms() {
		return froms;
	}

	public From getDefaultFrom() {
		return froms.get(0);
	}

	public From getFrom(String alias) {
		for (From from : froms) {
			if (alias.equals(from.getAlias())) {
				return from;
			}
		}
		return null;
	}

	public void setFroms(List<From> froms) {
		this.froms = froms;
	}

	public void addFrom(From from) {
		froms.add(from);
	}

	public void addFroms(List<From> froms) {
		this.froms.addAll(froms);
	}

	public List<Field> getGroupBys() {
		return groupBys;
	}

	public void setGroupBys(List<Field> groupBys) {
		this.groupBys = groupBys;
	}

	public void addGroupBy(Field groupBy) {
		groupBys.add(groupBy);
	}

	public void addGroupBys(List<Field> fields) {
		groupBys.addAll(fields);
	}

	public List<Condition> getHavings() {
		return havings;
	}

	public void setHavings(List<Condition> havings) {
		this.havings = havings;
	}

	public void addHaving(Condition condition) {
		havings.add(condition);
	}

	public void addHavings(List<Condition> havings) {
		this.havings.addAll(havings);
	}

	public List<String> getHints() {
		return hints;
	}

	public void setHints(List<String> hints) {
		this.hints = hints;
	}

	public void addHint(String hint) {
		hints.add(hint);
	}

	public void addHints(List<String> hints) {
		this.hints.addAll(hints);
	}

	/**
	 * Apply search info to this query info. Add conditions and sorts, override
	 * first row, page size and whether get total rows.
	 * 
	 * @param searchInfo
	 */
	public void applySearchInfo(SearchInfo searchInfo) {
		if (searchInfo != null) {
			this.setFirstRow(searchInfo.getFirstRow());
			this.setPageSize(searchInfo.getPageSize());
			this.setGetTotalRows(searchInfo.isGetTotalRows());
			getConditions().addAll(searchInfo.getConditions());
			getSorts().addAll(searchInfo.getSorts());
		}
	}

	public int getFetchSize() {
		if (fetchSize == 0) {
			fetchSize = Constants.DEFAULT_FETCH_SIZE;
		}
		if (fetchSize < getEndRow()) {
			fetchSize = getEndRow() + (getPageSize() * 2);
		}
		return fetchSize;
	}

	public String toString() {
		return getQL();
	}

	private void formlizeCondition(Object value) {
		if (value instanceof Condition) {
			Condition condition = (Condition) value;
			if (condition != null && condition.getField() != null
					&& condition.getField().getFrom() == null) {
				condition.getField().setFrom(getDefaultFrom());
			}
			formlizeCondition(condition.getValue());
		} else if (value instanceof Field && ((Field) value).getFrom() == null) {
			((Field) value).setFrom(getDefaultFrom());
		} else if (value instanceof List) {
			for (Object item : (List<?>) value) {
				formlizeCondition(item);
			}
		}
	}

	private void doFormlize() {
		setDefaultFrom(selects);
		setDefaultFrom(groupBys);
		setDefaultFromForConditions(getConditions());
		setDefaultFromForConditions(havings);
		List<Sort> sorts = getSorts();
		if (sorts != null && sorts.size() > 0) {
			for (Sort sort : sorts) {
				if (sort.getField().getFrom() == null) {
					sort.getField().setFrom(getDefaultFrom());
				}
			}
		}
	}

	public void reformlize() {
		doFormlize();
		formlized = true;
	}

	public void formlize() {
		if (formlized) {
			return;
		}
		doFormlize();
		formlized = true;
	}

	private void setDefaultFromForConditions(List<Condition> conditions) {
		if (conditions != null && conditions.size() > 0) {
			for (Condition condition : conditions) {
				formlizeCondition(condition);
			}
		}
	}

	private void setDefaultFrom(List<Field> fields) {
		if (fields != null && fields.size() > 0) {
			for (Field field : fields) {
				if (field.getFrom() == null) {
					field.setFrom(getDefaultFrom());
				}
			}
		}
	}

	public String getQL() {
		formlize();
		StringBuilder buffer = new StringBuilder();
		appendNotEmpty(buffer, selects, "select", ",");
		appendNotEmpty(buffer, froms, "from", ",");
		appendNotEmpty(buffer, getConditions(), "where", " and ");
		appendNotEmpty(buffer, groupBys, "group by", ",");
		appendNotEmpty(buffer, havings, "having", ",");
		appendNotEmpty(buffer, getSorts(), "order by", ",");
		appendNotEmpty(buffer, hints, "", "");
		buffer.deleteCharAt(0);// remove prefix blank of select
		return buffer.toString();
	}

	private void appendNotEmpty(StringBuilder buffer, List<?> list,
			String header, String separator) {
		if (list != null && list.size() > 0) {
			buffer.append(" ").append(header);
			if (header.length() > 0)
				buffer.append(" ");
			for (Object item : list) {
				buffer.append(item).append(separator);
			}
			if (separator.length() > 0)
				buffer.delete(buffer.length() - separator.length(), buffer
						.length());
		}
	}

	public String getCountQL() {
		formlize();
		StringBuilder buffer = new StringBuilder();
		buffer.append(" select count(distinct ");
		buffer.append(selects.get(0).getFieldName());
		buffer.append(") as ").append(COUNT_COLUMN);
		appendNotEmpty(buffer, froms, "from", ",");
		appendNotEmpty(buffer, getConditions(), "where", " and ");
		appendNotEmpty(buffer, groupBys, "group by", ",");
		appendNotEmpty(buffer, havings, "having", ",");
		appendNotEmpty(buffer, hints, "", "");
		buffer.deleteCharAt(0);// remove prefix blank of select
		return buffer.toString();
	}

	/**
	 * Deep clone.
	 * 
	 * @return copied query info.
	 */
	public QueryInfo copy() {
		try {
			ByteArrayOutputStream byteArrOs = new ByteArrayOutputStream();
			ObjectOutputStream objOs = new ObjectOutputStream(byteArrOs);
			objOs.writeObject(this);

			ByteArrayInputStream byteArrIs = new ByteArrayInputStream(byteArrOs
					.toByteArray());
			ObjectInputStream objIs = new ObjectInputStream(byteArrIs);
			Object deepCopy = objIs.readObject();
			return (QueryInfo) deepCopy;
		} catch (Exception e) {
			throw new RuntimeException("Can not copy object.", e);
		}
	}
}
